package com.ruoyi.util.security;

 import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson.JSON;
import com.ruoyi.common.utils.security.Md5Utils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.Security;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 威胁情报查询
 */
public class WxqbUtil {
    public static final Logger logger = LoggerFactory.getLogger(WxqbUtil.class);

    /**
     * 解决java不支持AES/CBC/PKCS7Padding模式解密
     */
    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    public static WxqbResult query(String host, String apikey, String salt,  Map<String, Object> params) throws Exception {

        long timeStamp = System.currentTimeMillis() / 1000;

        String type = Convert.toStr(params.get("type"));

        String sign = type + timeStamp + salt;

        logger.info("-host{}--apiKey:{}--salt:{}", host, apikey, salt);
        logger.info("---sign:{}", sign);

        sign = SecureUtil.md5(sign);

        logger.info("---MD5-sign:{}", sign);

       String result =  HttpRequest
                .get(host+"/v2/evaluate/")
                .header("X-Api-Key", apikey)
                .header("timestamp", timeStamp+"")
                .header("sign", sign)
                .form(params)
                .execute().body();

       logger.info("---result:{}", result);

        WxqbResult wxqbResult = JSON.parseObject(result, WxqbResult.class);
        return wxqbResult;

    }

    /**
     * 对字符串md5加密
     *
     * @param str
     * @return
     * @throws Exception
     */
    public static String getMD5Str(String str) throws Exception {
        try {
            // 生成一个MD5加密计算摘要
            MessageDigest md = MessageDigest.getInstance("MD5");
            // 计算md5函数
            md.update(str.getBytes());
            // digest()最后确定返回md5 hash值，返回值为8为字符串。因为md5 hash值是16位的hex值，实际上就是8位的字符
            // BigInteger函数则将8位的字符串转换成16位hex值，用字符串来表示；得到字符串形式的hash值
            return new BigInteger(1, md.digest()).toString(16);
        } catch (Exception e) {
            throw new Exception("MD5加密出现错误，"+e.toString());
        }
    }

    /**
     * 生成Token
     * @param appid
     * @param secret
     * @param thirduid
     * @return
     */
    public static String genToken(String appid, String secret, String thirduid) throws Exception {
        long timestamp = System.currentTimeMillis() / 1000; // 秒级时间戳
        String rand = RandomUtil.randomString(16); // 16位随机字符串

        logger.info("appid: {}", appid);
        logger.info("secret: {}", secret);
        logger.info("thirduid: {}", thirduid);
        logger.info("timestamp: {}", timestamp);
        logger.info("rand: {}", rand);
        String concatStr = thirduid + timestamp + rand + appid + secret;
        logger.info("拼接字符串: {}", concatStr);

//        String msdStr = DigestUtil.md5Hex(concatStr); // getMD5Str(concatStr);
        String msdStr = getMD5Str(concatStr);

        logger.info("MD5字符串: {}", msdStr);
        String sign = StrUtil.subSufByLength(msdStr, 16);
        logger.info("sign: {}", sign);


        // 1.将上述数据转换为json字符串
        Map<String, Object> params = new HashMap<>();
        params.put("rand", rand);
        params.put("sign", sign);
        params.put("third_uid", thirduid); // DigestUtil.md5Hex(appid + thirduid)
        params.put("timestamp", timestamp + "");

        logger.info("map: {}", params);
        String jsonStr = JSON.toJSONString(params);
        logger.info("jsonStr: {}", jsonStr);

        String token = encryptAES(jsonStr, secret, appid);

        logger.info("base64后token: {}", token);
        // 4.对token进行urlencode编码后，拼接到URL参数中
        token = URLEncoder.encode(token, "UTF-8");
        logger.info("encode后token: {}", token);
        return token;
    }

    /**
     * 加密数据
     * @param data 明文内容
     * @param key 密钥
     * @param iv 偏移
     * @return
     */
    private static String encryptAES(String data,String key,String iv) {
        try {

            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
            cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"), new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8)));
            byte[] result = cipher.doFinal(pkcs7padding(data, cipher.getBlockSize())); // 好气啊, 要自己手动实现 PKCS7Padding 填充
            return java.util.Base64.getEncoder().encodeToString(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "NULL";
    }

    /**
     * 解密数据
     * @param data 加密内容
     * @param key 密钥
     * @param iv 偏移
     * @return
     */
    private static String decryptAES(String data, String key,String iv) {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"), new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8)));
            byte[] result = cipher.doFinal(java.util.Base64.getDecoder().decode(data));
            return new String(result, StandardCharsets.UTF_8).trim();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "NULL";
    }

    /**
     * pkcs7填充
     * @param content
     * @param blockSize
     * @return
     */
    private static byte[] pkcs7padding(String content, int blockSize) {
        byte[] contentBytes = content.getBytes(StandardCharsets.UTF_8);
        int pad = blockSize - (contentBytes.length % blockSize); // 计算需要补位的长度
        byte padChr = (byte) pad; // 补位字符 (即用补位长度)
        byte[] paddedBytes = new byte[contentBytes.length + pad]; // 在原有的长度上加上补位长度
        System.arraycopy(contentBytes, 0, paddedBytes, 0, contentBytes.length); // 原有的先复制过去
        for (int i = contentBytes.length; i < paddedBytes.length; i++) { // 补位字符填充
            paddedBytes[i] = padChr;
        }
        return paddedBytes;
    }
}
